<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>列表拖动</title>
	<style>
		.item {
			cursor: move;
			user-select: none;
			display: flex;
			justify-content: center;
			align-items: center;
			width: 200px;
			height: 60px;
			border: 1px solid #cbd5e0;
			margin: 10px 0;
			box-sizing: border-box;
		}
		.placeholder {
			box-sizing: border-box;
			background-color: #edf2f7;
			margin: 10px 0;
			border: 2px dashed #cbd5e0;
		}
	</style>
</head>

<body>
	<div id="list">
		<div class="item">第一个元素</div>
		<div class="item">第二个元素</div>
		<div class="item">第三个元素</div>
		<div class="item">第四个元素</div>
		<div class="item">第五个元素</div>
	</div>
	<script>
		document.addEventListener('DOMContentLoaded', () => {
			const list = document.getElementById('list');
			list.querySelectorAll('.item').forEach(item => {
				item.addEventListener('mousedown', mouseDownHandler);
			});
			let draggingElement;
			let x = 0;
			let y = 0;
			let placeholder;
			let isDraggingStarted = false;
			function mouseDownHandler(e) {
				draggingElement = e.target;
				const rect = draggingElement.getBoundingClientRect();
				x = e.clientX - rect.left;
				y = e.clientY - rect.top;
				document.addEventListener('mousemove', mouseMoveHandler);
				document.addEventListener('mouseup', mouseUpHandler);
			};
			function mouseMoveHandler(e) {
				const draggingRect = draggingElement.getBoundingClientRect();
				if (!isDraggingStarted) {
					isDraggingStarted = true;
					placeholder = document.createElement('div');
					placeholder.classList.add('placeholder');
					draggingElement.parentNode.insertBefore(placeholder, draggingElement.nextSibling);
					placeholder.style.width = draggingRect.width + 'px';
					placeholder.style.height = draggingRect.height + 'px';
				}
				const left = e.clientX - x;
				const top = e.clientY - y;
				draggingElement.style.position = 'absolute';
				draggingElement.style.top = `${top}px`;
				draggingElement.style.left = `${left}px`;
				const prevEle = draggingElement.previousElementSibling;
				const nextEle = placeholder.nextElementSibling;
				if (prevEle && isAbove(draggingElement, prevEle)) {
					swap(placeholder, draggingElement);
					swap(placeholder, prevEle);
					return;
				}
				if (nextEle && !isAbove(draggingElement, nextEle)) {
					swap(nextEle, placeholder);
					swap(nextEle, draggingElement);
				}
			};
			function mouseUpHandler() {
				placeholder && placeholder.parentNode.removeChild(placeholder);
				isDraggingStarted = false;
				draggingElement.style.removeProperty('top');
				draggingElement.style.removeProperty('left');
				draggingElement.style.removeProperty('position');
				x = 0;
				y = 0;
				draggingElement = null;
				document.removeEventListener('mousemove', mouseMoveHandler);
				document.removeEventListener('mouseup', mouseUpHandler);
			}
			function isAbove(nodeA, nodeB) {
				const rectA = nodeA.getBoundingClientRect();
				const rectB = nodeB.getBoundingClientRect();
				const centerPointA = rectA.top + rectA.height / 2;
				const centerPointB = rectB.top + rectB.height / 2;
				return centerPointA < centerPointB;
			};
			function swap(nodeA, nodeB) {
				const parentA = nodeA.parentNode;
				const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling;
				nodeB.parentNode.insertBefore(nodeA, nodeB);
				parentA.insertBefore(nodeB, siblingA);
			};
		});
	</script>
</body>

</html>